using System;
using System.ComponentModel.Composition;
using System.Diagnostics;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio;
using System.Runtime.InteropServices;

namespace PostscriptTools.Intellisense {
	#region Command Filter

	[Export(typeof(IVsTextViewCreationListener))]
	[ContentType("postscript")]
	[TextViewRole(PredefinedTextViewRoles.Interactive)]
	internal sealed class VsTextViewCreationListener : IVsTextViewCreationListener {
		[Import]
		IVsEditorAdaptersFactoryService AdaptersFactory = null;

		[Import]
		ICompletionBroker CompletionBroker = null;

		public void VsTextViewCreated(IVsTextView textViewAdapter) {
			IWpfTextView view = AdaptersFactory.GetWpfTextView(textViewAdapter);
			Debug.Assert(view != null);

			var filter = new CommandFilter(view, CompletionBroker);

			IOleCommandTarget next;
			textViewAdapter.AddCommandFilter(filter, out next);
			filter.Next = next;
		}
	}

	/// <summary>
	/// A command filter that watches for the typing of AUTOCOMPLETE command to start a completion session
	/// </summary>
	internal sealed class CommandFilter : IOleCommandTarget {
		ICompletionSession _currentSession;

		public CommandFilter(IWpfTextView textView, ICompletionBroker broker) {
			_currentSession = null;
			TextView = textView;
			Broker = broker;
		}

		public IWpfTextView TextView { get; private set; }
		public ICompletionBroker Broker { get; private set; }
		public IOleCommandTarget Next { get; set; }

		private char GetTypeChar(IntPtr pvaIn) {
			return (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
		}

		public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) {
			bool handled = false;
			bool updateFilter = false;
			bool previousSessionActive = (_currentSession != null);
			int hresult = VSConstants.S_OK;

			// 1. Pre-process
			if (pguidCmdGroup == VSConstants.VSStd2K) {
				switch ((VSConstants.VSStd2KCmdID)nCmdID) {
					case VSConstants.VSStd2KCmdID.AUTOCOMPLETE:
					case VSConstants.VSStd2KCmdID.COMPLETEWORD:
						handled = StartSession();
						break;
					case VSConstants.VSStd2KCmdID.RETURN:
						handled = Complete(force: false); 
						break;
					case VSConstants.VSStd2KCmdID.TAB:
						handled = Complete(force: true);
						break;
					//case VSConstants.VSStd2KCmdID.TYPECHAR:
					//    char ch = GetTypeChar(pvaIn);
					//    if (ch == '/') {
					//        handled = Complete(false);
					//    }
					//    break;
					case VSConstants.VSStd2KCmdID.BACKSPACE:
						// update the filter after processing the key
						updateFilter = true;
						break;
					case VSConstants.VSStd2KCmdID.CANCEL:
						handled = Cancel();
						break;
				}
			}
			if (!handled) {
				hresult = Next.Exec(pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
				// update the filter after processing the key
				if (updateFilter) {
					Filter();
				}
			}
			if (ErrorHandler.Succeeded(hresult)) {
				if (pguidCmdGroup == VSConstants.VSStd2K) {
					switch ((VSConstants.VSStd2KCmdID)nCmdID) {
						case VSConstants.VSStd2KCmdID.TYPECHAR:
//							char ch = GetTypeChar(pvaIn);
							if (_currentSession != null)
								Filter();
//							else if ((ch == '/') && (!previousSessionActive)) {
//								StartSession();
//							}
							break;
					}
				}
			}
			return hresult;
		}

		private void Filter() {
			if (_currentSession == null) {
				return;
			}
			_currentSession.SelectedCompletionSet.Filter();
			_currentSession.SelectedCompletionSet.SelectBestMatch();
			_currentSession.SelectedCompletionSet.Recalculate();
		}

		bool Cancel() {
			if (_currentSession == null) {
				return false;
			}
			_currentSession.Dismiss();
			return true;
		}

		bool Complete(bool force) {
			if (_currentSession == null) {
				return false;
			}
			if (!_currentSession.SelectedCompletionSet.SelectionStatus.IsSelected && !force) {
				_currentSession.Dismiss();
				return false;
			} else {
				_currentSession.Commit();
				return true;
			}
		}

		bool StartSession() {
			if (_currentSession != null) {
				return false;
			}
			SnapshotPoint caret = TextView.Caret.Position.BufferPosition;
			ITextSnapshot snapshot = caret.Snapshot;
			_currentSession = Broker.CreateCompletionSession(TextView, snapshot.CreateTrackingPoint(caret, PointTrackingMode.Positive), true);
			_currentSession.Start();
			_currentSession.Dismissed += (sender, args) => _currentSession = null;
			return true;
		}

		public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) {
			if (pguidCmdGroup == VSConstants.VSStd2K) {
				switch ((VSConstants.VSStd2KCmdID)prgCmds[0].cmdID) {
					case VSConstants.VSStd2KCmdID.AUTOCOMPLETE:
					case VSConstants.VSStd2KCmdID.COMPLETEWORD:
						prgCmds[0].cmdf = (uint)OLECMDF.OLECMDF_ENABLED | (uint)OLECMDF.OLECMDF_SUPPORTED;
						return VSConstants.S_OK;
				}
			}
			return Next.QueryStatus(pguidCmdGroup, cCmds, prgCmds, pCmdText);
		}
	}

	#endregion
}
